
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using ZuDoKu.Utilities;
#endregion

namespace ZuDoKu
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public abstract class Control : DrawableGameComponent
    {
        #region Member fields and properties
        #region X / Y / Z
        private int _x, _y, _z;

        public virtual int X
        {
            get { return _x; }
            set { _x = value; }
        }

        public virtual int Y
        {
            get { return _y; }
            set { _y = value; }
        }

        public virtual int Z
        {
            get { return _z; }
            set { _z = value; }
        }
        #endregion

        #region Height / Width
        private int _width;
        public virtual int Width
        {
            get
            {
                return (_texture == null || _width > 0 ? _width : _texture.Width);
            }
            set{ _width = value;}
        }

        private int _height;
        public virtual int Height
        {
            get
            {
                return (_texture == null || _height > 0 ? _height : _texture.Height);
            }
            set{_height = value;}
        }
        #endregion

        #region Rotation / Origin
        private float _rotation;
        public float Rotation
        {
            get { return _rotation; }
            set { _rotation = value; }
        }
        private Vector2 _origin;
        public Vector2 Origin
        {
            get { return _origin; }
            set { _origin = value; }
        }
        #endregion

        #region States and states
        Rectangle _source;

        private int _state;
        protected int State
        {
            get { return _state; }
            set
            {
                _state = value;
                CalculateSourceRectangle();
            }
        }
        private int _numStates = 1;
        protected int NumStates
        {
            get { return _numStates; }
            set 
            { 
                _numStates = value;
                CalculateSourceRectangle();
            }
        }
        private int _frame;
        protected int Frame
        {
            get { return _frame; }
            set 
            { 
                _frame = value;
                CalculateSourceRectangle();
            }
        }

        private int _numFrames = 1;
        protected int NumFrames
        {
            get { return _numFrames; }
            set 
            { 
                _numFrames = value;
                CalculateSourceRectangle();
            }
        }
        #endregion

        #region Animation
        private bool _animated;
        public bool Animated
        {
            get { return _animated; }
            set
            {
                _animated = value;
                _lastFrameUpdate = DateTime.Now;
            }
        }
        private AnimationType _animationType;
        public AnimationType AnimationType
        {
            get { return _animationType; }
            set { _animationType = value; }
        }

        private double _animationSpeed = 1d;
        /// <summary>
        /// How long, in seconds, 1 loop through all frames should last.
        /// </summary>
        public double AnimationSpeed
        {
            get { return _animationSpeed; }
            set { _animationSpeed = value; }
        }
        #endregion

        #region Texture
        Texture2D _texture;
        protected Texture2D Texture
        {
            get { return _texture; }
            set 
            { 
                _texture = value;
                CalculateSourceRectangle();
            }
        }

        private byte _transparency = 255;
        /// <summary>
        /// A value between 0 and 255. 0 being completely transparent and 255 being completely opaque.
        /// </summary>
        public int Transparency
        {
            get { return (int)_transparency; }
            set { _transparency = (byte)MathHelper.Clamp((float)value, 0f, 255f); }
        }

        private float _scale = 1f;
        public float Scale
        {
            get{return _scale;}
            set{_scale = value; }
        }

        private SpriteEffects _effects = SpriteEffects.None;
        public SpriteEffects Effects
        {
            get{return _effects;}
            set{_effects = value;}
        }

        #endregion

        #region Controls

        private bool _acceptsInput = true;
        public bool AcceptsInput
        {
            get { return _acceptsInput; }
            set { _acceptsInput = value; }
        }

        private Screen _screen;
        public Screen Screen
        {
            get { return _screen; }
            internal set 
            { 
                _screen = value;
                RemoveCollection.Screen = value;
                Controls.Screen = value;
            }
        }

        private bool _isContainer;
        protected bool IsContainer
        {
            get { return _isContainer; }
            set { _isContainer = value; }
        }

        private Control _focused;
        public Control Focused
        {
            get { return _focused; }
            set { _focused = value; }
        }

        private ControlCollection _removeCollection;
        protected ControlCollection RemoveCollection
        {
            get 
            {
                if (_removeCollection == null)
                    _removeCollection = new ControlCollection(_screen);

                return _removeCollection; 
            }
        }

        private ControlCollection _controls;
        protected ControlCollection Controls
        {
            get 
            {
                if (_controls == null)
                {
                    _controls = new ControlCollection(_screen);
                    _controls.Added += new EventHandler<ZuDoKu.Utilities.EventArgs<Control>>(control_Added);
                }
                return _controls; 
            }
        }

        #endregion
        #endregion

        #region Constructors
        public Control(Texture2D texture)
            : this(texture, 0, 0, 1, 1)
        { }
        public Control(Texture2D texture, int width, int height, int numFrames, int numStates)
            : this()
        {
            _texture = texture;
            _width = width;
            _height = height;
            _numFrames = numFrames;
            _numStates = numStates;
        }

        public Control()
            : base(MainGame.Instance)
        { }

        void control_Added(object sender, EventArgs<Control> e)
        {
            e.Arg.DrawOrder += DrawOrder;
        }
        #endregion

        #region Initialize
        public override void Initialize()
        {
            CalculateSourceRectangle();

            foreach (Control c in Controls)
            {
                c.Initialize();
            }
            base.Initialize();
        }
        #endregion

        #region Draw
        
        private void CalculateSourceRectangle()
        {
            if (_texture != null)
            {
                int sourceWidth = (int)((double)_texture.Width / (double)_numFrames);
                int sourceHeight = (int)((double)_texture.Height / (double)_numStates);

                _source = new Rectangle(
                    _frame * sourceWidth,
                    _state * sourceHeight,
                    sourceWidth,
                    sourceHeight);
            }
        }

        public virtual void Draw(SpriteBatch batch, GameTime gameTime, int offsetX, int offsetY, byte transparency)
        {
            if (Visible)
            {
                //This controls own transparency is modified by it's parents.
                byte inheritedTransparency = (byte)MathHelper.Clamp((float)transparency - (float)(255 - _transparency), 0, 255);

                if (_texture != null)
                {
                    Rectangle destination = new Rectangle(offsetX + X, offsetY + Y, Width, Height);

                    batch.Draw(
                        _texture,
                        destination,
                        _source,
                        new Color(255, 255, 255, inheritedTransparency));
                }

                foreach (Control c in Controls)
                {
                    if (_isContainer)
                        c.Draw(batch, gameTime, offsetX + X, offsetY + Y, inheritedTransparency);
                    else
                        c.Draw(batch, gameTime, offsetX, offsetY, inheritedTransparency);
                }
            }
        }

        public override void Draw(GameTime gameTime)
        {
            SpriteBatch batch = MainGame.Instance.SpriteBatch;
            batch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred, SaveStateMode.None);
            Draw(batch, gameTime, 0, 0, 255);
            batch.End();
        }
        #endregion

        #region Update
        private bool _animateStepForward = true;
        private DateTime _lastFrameUpdate;
        public override void Update(GameTime gameTime)
        {
            if (Visible)
            {
                if (_animated)
                {
                    TimeSpan elapsed = DateTime.Now.Subtract(_lastFrameUpdate);

                    //This is how many frames per millisecond are displayed.
                    double fps = (1000 * AnimationSpeed) / NumFrames;

                    //Increment the number of frames by how many milliseconds have elapsed.
                    int frameIncrement = (int)Math.Floor(elapsed.TotalMilliseconds / fps);

                    if (frameIncrement > 0)
                    {
                        if (_animationType == AnimationType.Loop)
                        {
                            Frame += frameIncrement;

                            // When the animation starts back at the beginning
                            if (Frame >= NumFrames)
                                OnAnimationLooping();
                        }
                        else
                        {
                            if (_animateStepForward)
                            {
                                if (Frame + frameIncrement >= NumFrames)
                                {
                                    OnAnimationLooping();
                                    _animateStepForward = false;
                                    Frame -= frameIncrement;
                                }
                                else Frame += frameIncrement;
                            }
                            else
                            {
                                if (Frame - frameIncrement <= 0)
                                {
                                    OnAnimationLooping();
                                    _animateStepForward = true;
                                    Frame += frameIncrement;
                                }
                                else Frame -= frameIncrement;
                            }
                        }

                        Frame %= NumFrames;
                        _lastFrameUpdate = DateTime.Now;
                    }
                }

                foreach (Control c in Controls)
                {
                    c.Update(gameTime);
                }

                foreach (Control c in RemoveCollection)
                {
                    Controls.Remove(c);
                }
                RemoveCollection.Clear();

                base.Update(gameTime);
            }
        }

        protected virtual void ProcessInput(GamePadState state)
        {
            if (_focused != null)
                _focused.ProcessInput(state);
        }

        public event EventHandler AnimationLooping;
        protected void OnAnimationLooping()
        {
            if (AnimationLooping != null) AnimationLooping(this, EventArgs.Empty);
        }
        #endregion
    }
}


